package org.loong.crypto.service;

import java.security.KeyPair;
import java.util.Objects;

import javax.crypto.SecretKey;

import org.loong.crypto.common.exception.CryptoException;
import org.loong.crypto.core.algorithm.CipherAlgorithm;
import org.loong.crypto.core.algorithm.HmacAlgorithm;
import org.loong.crypto.core.algorithm.KeyAlgorithm;
import org.loong.crypto.core.algorithm.KeyPairAlgorithm;
import org.loong.crypto.core.algorithm.MessageDigestAlgorithm;
import org.loong.crypto.core.algorithm.SignatureAlgorithm;
import org.loong.crypto.core.params.CipherParameters;
import org.loong.crypto.core.params.HmacParameters;
import org.loong.crypto.core.params.KeyGeneratorParameters;
import org.loong.crypto.core.params.KeyPairGeneratorParameters;
import org.loong.crypto.core.params.SignatureParameters;
import org.loong.crypto.service.core.provider.Decrypter;
import org.loong.crypto.service.core.provider.Encrypter;
import org.loong.crypto.service.core.provider.HmacSigner;
import org.loong.crypto.service.core.provider.HmacVerifier;
import org.loong.crypto.service.core.provider.KeyGenerator;
import org.loong.crypto.service.core.provider.KeyPairGenerator;
import org.loong.crypto.service.core.provider.MessageDigest;
import org.loong.crypto.service.core.provider.ProviderFactory;
import org.loong.crypto.service.core.provider.RandomGenerator;
import org.loong.crypto.service.core.provider.Signer;
import org.loong.crypto.service.core.provider.Verifier;

public class GenericCryptoService implements CryptoService {

    private final ProviderFactory providerFactory;

    public GenericCryptoService(final ProviderFactory providerFactory) {
        this.providerFactory = providerFactory;
    }
    
    @Override
    public byte[] generateRandom(int randomLength) throws CryptoException {
        RandomGenerator randomGenerator = this.providerFactory.createRandomGenerator();
        if (Objects.isNull(randomGenerator)) {
            throw new CryptoException("Not found random generator provider");
        }
        
        return randomGenerator.generateRandom(randomLength);
    }
    
    @Override
    public SecretKey generateKey(KeyAlgorithm algorithm, KeyGeneratorParameters params) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        KeyGenerator keyGenerator = this.providerFactory.createKeyGenerator(algorithm);
        if (Objects.isNull(keyGenerator)) {
            throw new CryptoException("Not found key generator provider");
        }

        if (!keyGenerator.supportedKeyAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return keyGenerator.generateKey(algorithm, params.getKeySize());
    }

    @Override
    public KeyPair generateKeyPair(KeyPairAlgorithm algorithm, KeyPairGeneratorParameters params) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        KeyPairGenerator keyPairGenerator = this.providerFactory.createKeyPairGenerator(algorithm);
        if (Objects.isNull(keyPairGenerator)) {
            throw new CryptoException("Not found key pair generator provider");
        }

        if (!keyPairGenerator.supportedKeyPairAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return keyPairGenerator.generateKeyPair(algorithm, params.getKeySize());
    }

    @Override
    public byte[] digest(MessageDigestAlgorithm algorithm, byte[] data) throws CryptoException {
        if (Objects.isNull(algorithm)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        MessageDigest messageDigest = this.providerFactory.createMessageDigest(algorithm, null);
        if (Objects.isNull(messageDigest)) {
            throw new CryptoException("Not found message digest provider");
        }

        if (!messageDigest.supportedMessageDigestAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return messageDigest.digest(algorithm, data);
    }

    @Override
    public byte[] hmac(HmacAlgorithm algorithm, HmacParameters params, byte[] message) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        HmacSigner hmacSigner = this.providerFactory.createHmacSigner(algorithm, params);
        if (Objects.isNull(hmacSigner)) {
            throw new CryptoException("Not found hmac provider");
        }

        if (!hmacSigner.supportedHmacAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return hmacSigner.sign(algorithm, message);
    }

    @Override
    public boolean verifyHmac(HmacAlgorithm algorithm, HmacParameters params, byte[] message, byte[] hmacToCompare) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        HmacVerifier hmacVerifier = this.providerFactory.createHmacVerifier(algorithm, params);
        if (Objects.isNull(hmacVerifier)) {
            throw new CryptoException("Not found hmac provider");
        }

        if (!hmacVerifier.supportedHmacAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return hmacVerifier.verify(algorithm, message, hmacToCompare);
    }

    @Override
    public byte[] encrypt(CipherAlgorithm algorithm, CipherParameters params, byte[] data) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        Encrypter encrypter = this.providerFactory.createEncrypter(algorithm, params);
        if (Objects.isNull(encrypter)) {
            throw new CryptoException("Not found encrypter provider");
        }

        if (!encrypter.supportedCipherAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return encrypter.encrypt(algorithm, data);
    }

    @Override
    public byte[] decrypt(CipherAlgorithm algorithm, CipherParameters params, byte[] cipherText) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        Decrypter decrypter = this.providerFactory.createDecrypter(algorithm, params);
        if (Objects.isNull(decrypter)) {
            throw new CryptoException("Not found decrypter provider");
        }

        if (!decrypter.supportedCipherAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return decrypter.decrypt(algorithm, cipherText);
    }

    @Override
    public byte[] sign(SignatureAlgorithm algorithm, SignatureParameters params, byte[] data) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        Signer signer = this.providerFactory.createSigner(algorithm, params);
        if (Objects.isNull(signer)) {
            throw new CryptoException("Not found signer provider");
        }

        if (!signer.supportedSignatureAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return signer.sign(algorithm, data);
    }

    @Override
    public boolean verify(SignatureAlgorithm algorithm, SignatureParameters params, byte[] data, byte[] signature) throws CryptoException {
        if (Objects.isNull(algorithm) || Objects.isNull(params)) {
            throw new CryptoException("The algorithm or params must not be null");
        }

        if (Objects.isNull(this.providerFactory)) {
            throw new CryptoException("No provider factory configured");
        }

        Verifier verifier = this.providerFactory.createVerifier(algorithm, params);
        if (Objects.isNull(verifier)) {
            throw new CryptoException("Not found verifier provider");
        }

        if (!verifier.supportedSignatureAlgorithms().contains(algorithm)) {
            throw new CryptoException("The " + algorithm.getName() + " algorithm is not allowed or supported by the provider factory");
        }
        return verifier.verify(algorithm, data, signature);
    }
    
    /**
     * Gets the value of providerFactory.
     *
     * @return the value of providerFactory
     */
    public ProviderFactory getProviderFactory() {
        return providerFactory;
    }
}
